URPでDDX DDYの処理を見る

June 12, 2022


シェーダー内でDDX、DDYメソッドを見たことがありますがピンと来てなかったところ 過去のUnity道場の資料を見て雰囲気をつかめたので処理を抜粋

// こんな感じの
ddx(i.uv)
ddy(i.uv)

https://www.slideshare.net/UnityTechnologiesJapan/unity-2-133372753

「DDX、DDYメソッドは隣接するピクセル情報との差分を求める」 しかしフラグメントシェーダーは並列して処理しているため、他のピクセル情報を取得できるのか。 というのは疑問点でありましたが、上記資料ではフラグメントシェーダーは2x2の4ピクセル同時に処理されているようでした

F23DE75D49D0BFB3E852006AB1955DF2 1FC0B7C4763E9B0D333C49E174557995

つまり、同時に実行されていることからDDX、DDYでは隣接するピクセルの情報との差分が取得できるということのようです メモリもローカルに乗ったままなので処理速度も遅くないとのこと

更に面白かったのが以下の処理で FC60D84ABACC8360DE5E40E553D00E35

fwidthメソッドが隣接するX、Yのピクセル差分を絶対値で足し合わせているメソッドのようで

fwidth(v) = abs(ddx(v)) + abs(ddy(v))

こちらにColor値を入れてそれを返していました。 その結果がみぎのような輪郭が黒く表示されるシェーダーとなるとのこと。

つまり隣接するピクセルの差分同士を足し合わせてそれをフラグメントシェーダーの結果として返しています。 自分が不透明なピクセルであるとき、隣接するピクセルのColorアルファ値が同じく不透明であると 1 - 1 = 0(差分) でアルファ値が0となり透明化。

しかし、隣接が透明(α値0)との差分の場合 1 - 0 = 1 と、結果としてアルファ値が1で出力されます

なので上記の場合、アルファの差分が出る輪郭のみが表示されるようです

前回のURPシェーダーに手を加えた結果が以下

Shader "Custom/DDX_DDY"
{
	Properties
	{
		_MainTex ("Main Tex", 2D) = "while" {}
	}

	SubShader
	{ 
		Tags
		{ 
			"RenderType" = "Transparent"
            "RenderPipeline" = "UniversalPipeline"
            "IgnoreProjector" = "True"
			"Queue" = "Transparent"
		}
		Blend SrcAlpha OneMinusSrcAlpha 

		Pass
		{
			HLSLPROGRAM
			#pragma vertex vert
			#pragma fragment flag

			// HLSLのマクロやメソッドの定義
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"

			struct Attributes
			{
				float4 positionOS : POSITION;
				float2 uv : TEXCOORD0; // テクスチャ座標
			};
			struct Varyings
			{
				float4 positionHCS : SV_POSITION;
				float2 uv : TEXCOORD0; // テクスチャ座標
			};

			sampler2D _MainTex;

			// 頂点の出力
			Varyings vert(Attributes IN)
			{
				Varyings o;
				o.positionHCS = TransformObjectToHClip(IN.positionOS.xyz);
				o.uv = IN.uv;
				return o;
			}

			half4 flag(Varyings IN) : SV_Target
			{
				// 保管されたUV座標でテクスチャから色を得る
				half4 col = tex2D(_MainTex, IN.uv);

				return fwidth(col);
			}

       	 	ENDHLSL
		}
	}
}

38803738C6C8208647A147F204AA2B77

もう少し手を加えて、fwidthをそのまま帰すのではなくlerpで差分が出るとき(輪郭)のみ黒が描画されるようにした場合 EF0912C8BE96E9DAF4C8E2360BD1D20C

return lerp(col, fixed4(0, 0, 0, 1), fwidth(col.a));

画像の輪郭が描画されるようになりました。(腕や髪がわかりやすい 326BE9612F802AFFDB81DD485A923549

ddx, ddyの特性をつかむことができました